﻿

//这个接口由外部实现，可以兼容各种各样的3D lib

namespace FreeNode
{
    //export interface IParser
    //{
    //    ParseNode(parser: SceneParser, json: {}, parent: any): any;//处理节点

    //    ParseComponent(parser: SceneParser, json: {}, box: FreeNode.StreamBox, parent: any): any;//处理组件

    //    DelayParse(): void;
    //}
    export class Error//typescirpt 使用export关键字
    {
        constructor(_info: string)
        {
            this.info = _info;
        }
        public info: string;
    }
    export class loadTool
    {
        static loadText(url: string, fun: (_txt: string, _err: Error) => void): void
        {
            var req = new XMLHttpRequest();
            req.open("GET", url);
            req.onreadystatechange = () =>
            {
                if (req.readyState == 4)
                {
                    if (req.status == 404)
                    {
                        fun(null, new Error("got a 404:" + url));
                        return;
                    }
                    fun(req.responseText, null);
                }
            };
            req.onerror = () =>
            {
                fun(null, new Error("onerr in req:"));
            };
            req.send();
        }


        static loadArrayBuffer(url: string, fun: (_bin: ArrayBuffer, _err: Error) => void): void
        {
            var req = new XMLHttpRequest();

            req.open("GET", url);
            req.responseType = "arraybuffer";//ie 一定要在open之后修改responseType
            req.onreadystatechange = () =>
            {
                if (req.readyState == 4)
                {
                    if (req.status == 404)
                    {
                        fun(null, new Error("got a 404:" + url));
                        return;
                    }

                    //console.log("got bin:" + typeof (req.response) + req.responseType);
                    fun(req.response, null);
                }
            };
            req.onerror = () =>
            {
                fun(null, new Error("onerr in req:"));
            };
            req.send();
        }

        static loadBlob(url: string, fun: (_blob: Blob, _err: Error) => void): void
        {
            var req = new XMLHttpRequest();

            req.open("GET", url);
            req.responseType = "blob";//ie 一定要在open之后修改responseType
            req.onreadystatechange = () =>
            {
                if (req.readyState == 4)
                {
                    if (req.status == 404)
                    {
                        fun(null, new Error("got a 404:" + url));
                        return;
                    }

                    //console.log("got _blob:" + typeof (req.response) + req.responseType);
                    fun(req.response, null);
                }
            };
            req.onerror = () =>
            {
                fun(null, new Error("onerr in req:"));
            };
            req.send();
        }


    }
    export class SubMesh
    {
        drawstyle: number;
        indices: Uint32Array;
    }
    export class MeshData
    {
        name: string;

        vec3positions: Float32Array;
        vec3normals: Float32Array = null;
        vec4tangents: Float32Array = null;
        vec2uvs0: Float32Array = null;
        vec2uvs1: Float32Array = null;

        //以下两个数据并没什么卵用
        //vec2uvs2: Float32Array = null;
        //vec2uvs3: Float32Array;
        vec4colors: Float32Array = null;

        vec10tpose: Float32Array = null;//tpose 使用的是10个float32，pos xyz，scale xyz，quat xyzw
        vec8widget: Float32Array = null;
        //matricesIndices: number[];
        //matricesWeights: number[];
        indices: SubMesh[];

        static loadMesh(buf: ArrayBuffer): MeshData
        {
            var meshdata: MeshData = new MeshData();
            var read: Reader = new Reader(buf);
            meshdata.name = read.readString();

            var bound = read.readBound();


            var vcount = read.readUInt32();

            while (true)
            {
                var tag = read.readUInt8();
                if (tag == 255) break;
                if (tag == 1)//pos
                {
                    meshdata.vec3positions = new Float32Array(vcount * 3);
                    for (var i = 0; i < vcount; i++)
                    {
                        meshdata.vec3positions[i * 3 + 0] = read.readSingle();//x
                        meshdata.vec3positions[i * 3 + 1] = read.readSingle();//y
                        meshdata.vec3positions[i * 3 + 2] = read.readSingle();//z
                    }
                }
                else if (tag == 2)//color
                {
                    meshdata.vec4colors = new Float32Array(vcount * 4);
                    for (var i = 0; i < vcount; i++)
                    {
                        meshdata.vec4colors[i * 4 + 0] = read.readUInt8();//a
                        meshdata.vec4colors[i * 4 + 1] = read.readUInt8();//r
                        meshdata.vec4colors[i * 4 + 2] = read.readUInt8();//g
                        meshdata.vec4colors[i * 4 + 4] = read.readUInt8();//b
                    }
                }
                else if (tag == 3)//normal
                {
                    meshdata.vec3normals = new Float32Array(vcount * 3);
                    for (var i = 0; i < vcount; i++)
                    {
                        meshdata.vec3normals[i * 3 + 0] = read.readSingle();//x
                        meshdata.vec3normals[i * 3 + 1] = read.readSingle();//y
                        meshdata.vec3normals[i * 3 + 2] = read.readSingle();//z
                    }
                }
                else if (tag == 4)//uv
                {
                    meshdata.vec2uvs0 = new Float32Array(vcount * 2);
                    for (var i = 0; i < vcount; i++)
                    {
                        meshdata.vec2uvs0[i * 2 + 0] = read.readSingle();//u
                        meshdata.vec2uvs0[i * 2 + 1] = read.readSingle();//v

                    }
                }
                else if (tag == 5)//uv1
                {
                    meshdata.vec2uvs1 = new Float32Array(vcount * 2);
                    for (var i = 0; i < vcount; i++)
                    {
                        meshdata.vec2uvs1[i * 2 + 0] = read.readSingle();//u
                        meshdata.vec2uvs1[i * 2 + 1] = read.readSingle();//v

                    }
                }
                else if (tag == 6)//uv2
                {
                    //meshdata.vec2uvs2 = new Float32Array(vcount * 2);
                    for (var i = 0; i < vcount; i++)
                    {
                        //meshdata.vec2uvs2[i * 2 + 0] =
                        read.readSingle();//u
                        //meshdata.vec2uvs2[i * 2 + 1] =
                        read.readSingle();//v

                    }
                }
                else if (tag == 7)//tangent
                {
                    meshdata.vec4tangents = new Float32Array(vcount * 4);
                    for (var i = 0; i < vcount; i++)
                    {
                        meshdata.vec4tangents[i * 4 + 0] = read.readSingle();//x
                        meshdata.vec4tangents[i * 4 + 1] = read.readSingle();//y
                        meshdata.vec4tangents[i * 4 + 2] = read.readSingle();//z
                        meshdata.vec4tangents[i * 4 + 4] = read.readSingle();//w
                    }
                }
                else if (tag == 8)//uv3
                {
                    for (var i = 0; i < vcount; i++)
                    {
                        //meshdata.vec2uvs2[i * 2 + 0] =
                        read.readSingle();//u
                        //meshdata.vec2uvs2[i * 2 + 1] =
                        read.readSingle();//v

                    }
                }
                else if (tag == 16)//tpose
                {
                    var tposelen = read.readUInt8();
                    meshdata.vec10tpose = new Float32Array(tposelen * 10);
                    for (var i = 0; i < tposelen; i++)
                    {
                        meshdata.vec10tpose[i * 10 + 0] = read.readSingle();//posx;
                        meshdata.vec10tpose[i * 10 + 1] = read.readSingle();//posy;
                        meshdata.vec10tpose[i * 10 + 2] = read.readSingle();//posz;
                        meshdata.vec10tpose[i * 10 + 3] = read.readSingle();//scalex;
                        meshdata.vec10tpose[i * 10 + 4] = read.readSingle();//scaley;
                        meshdata.vec10tpose[i * 10 + 5] = read.readSingle();//scalez;
                        meshdata.vec10tpose[i * 10 + 6] = read.readSingle();//quatx;
                        meshdata.vec10tpose[i * 10 + 7] = read.readSingle();//quaty;
                        meshdata.vec10tpose[i * 10 + 8] = read.readSingle();//quatz;
                        meshdata.vec10tpose[i * 10 + 9] = read.readSingle();//quatw;
                    }
                }
                else if (tag == 17)//skinwidget;
                {
                    meshdata.vec8widget = new Float32Array(vcount * 8);
                    for (var i = 0; i < vcount; i++)
                    {
                        meshdata.vec8widget[i * 8 + 0] = read.readUInt32();//index0;
                        meshdata.vec8widget[i * 8 + 1] = read.readUInt32();//index1;
                        meshdata.vec8widget[i * 8 + 2] = read.readUInt32();//index2;
                        meshdata.vec8widget[i * 8 + 3] = read.readUInt32();//index3;
                        meshdata.vec8widget[i * 8 + 4] = read.readSingle();//widget0;
                        meshdata.vec8widget[i * 8 + 5] = read.readSingle();//widget1;
                        meshdata.vec8widget[i * 8 + 6] = read.readSingle();//widget2;
                        meshdata.vec8widget[i * 8 + 7] = read.readSingle();//widget3;
                    }
                }
                else
                {
                    throw "notwrite" + tag;
                }
            }

            var subcount = read.readUInt8();
            meshdata.indices = [];
            for (var i = 0; i < subcount; i++)
            {
                var tv = read.readUInt32();

                var sublen = read.readUInt32();
                meshdata.indices.push(new SubMesh());
                meshdata.indices[i].drawstyle = tv;
                meshdata.indices[i].indices = new Uint32Array(sublen);
                for (var j = 0; j < sublen; j++)
                {
                    meshdata.indices[i].indices[j] = read.readUInt32();
                }

            }
            return meshdata;
            //贴图的加载是特殊的，不能全用二进制统一处理了
            //var crateImage: HTMLImageElement = new HTMLImageElement();
            //crateImage.src = "";

        }

    }
    export class Reader
    {
        buf: ArrayBuffer;
        data: DataView;
        constructor(buf: ArrayBuffer, seek: number = 0)
        {
            this.seek = seek;
            this.buf = buf;
            this.data = new DataView(this.buf, seek);
        }
        seek: number;
        readString(): string
        {
            var slen = this.data.getUint8(this.seek);
            this.seek++;
            var bs: string = "";
            for (var i = 0; i < slen; i++)
            {
                bs += String.fromCharCode(this.data.getUint8(this.seek));
                this.seek++;
            }
            return bs;
        }
        readBound(): any
        {
            this.seek += 24;
            return null;
        }
        readUInt32(): number
        {
            var num = this.data.getUint32(this.seek, true);
            this.seek += 4;
            return num;
        }
        readSingle(): number
        {
            var num = this.data.getFloat32(this.seek, true);
            this.seek += 4;
            return num;
        }
        readUInt8(): number
        {
            var num = this.data.getUint8(this.seek);
            this.seek += 1;
            return num;
        }
    }
    export class Vector3
    {
        constructor(x: number, y: number, z: number)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }
        x: number;
        y: number;
        z: number;
        Clone(): Vector3
        {
            return new Vector3(this.x, this.y, this.z);
        }
    }
    export class Quaternion
    {
        constructor(_x: number, _y: number, _z: number, _w: number)
        {
            this.x = _x;
            this.y = _y;
            this.z = _z;
            this.w = _w;
        }
        x: number;
        y: number;
        z: number;
        w: number;
        Clone(): Quaternion
        {
            return new Quaternion(this.x, this.y, this.z, this.w);
        }
        public transformVector(vector: Vector3, target: Vector3 = null): Vector3
        {
            var x1: number, y1: number, z1: number, w1: number;
            var x2: number = vector.x, y2: number = vector.y, z2: number = vector.z;

            if (target === null)
            {
                target = new Vector3(0, 0, 0);
            }

            // p*q'
            w1 = -this.x * x2 - this.y * y2 - this.z * z2;
            x1 = this.w * x2 + this.y * z2 - this.z * y2;
            y1 = this.w * y2 - this.x * z2 + this.z * x2;
            z1 = this.w * z2 + this.x * y2 - this.y * x2;

            target.x = -w1 * this.x + x1 * this.w - y1 * this.z + z1 * this.y;
            target.y = -w1 * this.y + x1 * this.z + y1 * this.w - z1 * this.x;
            target.z = -w1 * this.z - x1 * this.y + y1 * this.x + z1 * this.w;
            return target;
        }

        public inverse(target: Quaternion = null): Quaternion
        {
            if (!target)
            {
                target = new Quaternion(0, 0, 0, 1);
            }

            var norm: number = this.w * this.w + this.x * this.x + this.y * this.y + this.z * this.z;

            if (norm > 0.0)
            {
                var invNorm = 1.0 / norm;
                target.w = this.w * invNorm;
                target.x = -this.x * invNorm;
                target.y = -this.y * invNorm;
                target.z = -this.z * invNorm;
            }

            return target;
        }
        static sMuliply(left: Quaternion, right: Quaternion, target: Quaternion = null): Quaternion
        {
            var w1: number = left.w, x1: number = left.x, y1: number = left.y, z1: number = left.z;
            var w2: number = right.w, x2: number = right.x, y2: number = right.y, z2: number = right.z;

            //multi quad
            target.w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2;
            target.x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2;
            target.y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2;
            target.z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2;


            return target;
        }

        public slerp(qa: Quaternion, qb: Quaternion, t: number)
        {
            var w1: number = qa.w, x1: number = qa.x, y1: number = qa.y, z1: number = qa.z;
            var w2: number = qb.w, x2: number = qb.x, y2: number = qb.y, z2: number = qb.z;
            var dot: number = w1 * w2 + x1 * x2 + y1 * y2 + z1 * z2;

            // shortest direction
            if (dot < 0)
            {
                dot = -dot;
                w2 = -w2;
                x2 = -x2;
                y2 = -y2;
                z2 = -z2;
            }

            if (dot < 0.95)
            {
                // interpolate angle linearly
                var angle: number = Math.acos(dot);
                var s: number = 1 / Math.sin(angle);
                var s1: number = Math.sin(angle * (1 - t)) * s;
                var s2: number = Math.sin(angle * t) * s;
                this.w = w1 * s1 + w2 * s2;
                this.x = x1 * s1 + x2 * s2;
                this.y = y1 * s1 + y2 * s2;
                this.z = z1 * s1 + z2 * s2;
            } else
            {
                // nearly identical angle, interpolate linearly
                this.w = w1 + t * (w2 - w1);
                this.x = x1 + t * (x2 - x1);
                this.y = y1 + t * (y2 - y1);
                this.z = z1 + t * (z2 - z1);
                var len: number = 1.0 / Math.sqrt(this.w * this.w + this.x * this.x + this.y * this.y + this.z * this.z);
                this.w *= len;
                this.x *= len;
                this.y *= len;
                this.z *= len;
            }
        }
        public lerp(qa: Quaternion, qb: Quaternion, t: number)
        {
            var w1: number = qa.w, x1: number = qa.x, y1: number = qa.y, z1: number = qa.z;
            var w2: number = qb.w, x2: number = qb.x, y2: number = qb.y, z2: number = qb.z;
            var len: number;

            // shortest direction
            if (w1 * w2 + x1 * x2 + y1 * y2 + z1 * z2 < 0)
            {
                w2 = -w2;
                x2 = -x2;
                y2 = -y2;
                z2 = -z2;
            }

            this.w = w1 + t * (w2 - w1);
            this.x = x1 + t * (x2 - x1);
            this.y = y1 + t * (y2 - y1);
            this.z = z1 + t * (z2 - z1);

            len = 1.0 / Math.sqrt(this.w * this.w + this.x * this.x + this.y * this.y + this.z * this.z);
            this.w *= len;
            this.x *= len;
            this.y *= len;
            this.z *= len;
        }
    }
    export class SubClip
    {
        name: string = "noname";
        loop: boolean;

        startframe: number;
        endframe: number;
    }
    export class PoseBoneMatrix
    {
        t: Vector3;
        r: Quaternion;
        Clone(): PoseBoneMatrix
        {
            var p = new PoseBoneMatrix();
            p.t = this.t.Clone();
            p.r = this.r.Clone();
            return p;
        }
        load(read: Reader, last: PoseBoneMatrix)
        {
            {
                var x = read.readSingle();
                var y = read.readSingle();
                var z = read.readSingle();
                var w = read.readSingle();
                this.r = new Quaternion(x, y, z, w);
            }
            {
                var x = read.readSingle();
                var y = read.readSingle();
                var z = read.readSingle();
                this.t = new Vector3(x, y, z);
            }
        }
        static createDefault(): PoseBoneMatrix
        {
            var pt = new PoseBoneMatrix();
            pt.r = new Quaternion(0, 0, 0, 1);
            pt.t = new Vector3(0, 0, 0);
            return pt;
        }
        copyFrom(src: PoseBoneMatrix)
        {
            this.r.x = src.r.x;
            this.r.y = src.r.y;
            this.r.z = src.r.z;
            this.r.w = src.r.w;
            this.t.x = src.t.x;
            this.t.y = src.t.y;
            this.t.z = src.t.z;
        }
        invert()
        {
            this.r = this.r.inverse();
            this.t = this.r.transformVector(this.t);
            this.t.x *= -1;
            this.t.y *= -1;
            this.t.z *= -1;
        }
        lerpInWorld(_tpose: PoseBoneMatrix, from: PoseBoneMatrix, to: PoseBoneMatrix, v: number)
        {
            ////预乘之后，插值奇慢
            var tpose = new egret3d.Matrix4_4();
            tpose.makeTransform(new egret3d.Vector3D(_tpose.t.x, _tpose.t.y, _tpose.t.z),
                new egret3d.Vector3D(1, 1, 1),
                new egret3d.Quaternion(_tpose.r.x, _tpose.r.y, _tpose.r.z, _tpose.r.w)
            );

            var t1 = PoseBoneMatrix.sMultiply(from, _tpose);
            var t2 = PoseBoneMatrix.sMultiply(to, _tpose);
            //球插
            var outLerp = PoseBoneMatrix.sLerp(t1, t2, v);

            //再去掉tpose，为了加速这个过程，考虑要存一份 合并tpose的骨骼数据

            var itpose = _tpose.Clone();
            itpose.invert();

            PoseBoneMatrix.sMultiply(outLerp, itpose, this);
        }
        static sMultiply(left: PoseBoneMatrix, right: PoseBoneMatrix, target: PoseBoneMatrix = null): PoseBoneMatrix
        {
            if (target == null)
                target = PoseBoneMatrix.createDefault();
            var dir = right.t.Clone();
            var dirtran = left.r.transformVector(dir);
            target.t.x = dirtran.x + left.t.x;
            target.t.y = dirtran.y + left.t.y;
            target.t.z = dirtran.z + left.t.z;
            Quaternion.sMuliply(left.r, right.r, target.r);
            return target;
        }
        static sLerp(left: PoseBoneMatrix, right: PoseBoneMatrix, v: number, target: PoseBoneMatrix = null): PoseBoneMatrix
        {
            if (target == null)
                target = PoseBoneMatrix.createDefault();
            target.t.x = left.t.x * (1 - v) + right.t.x * v;
            target.t.y = left.t.y * (1 - v) + right.t.y * v;
            target.t.z = left.t.z * (1 - v) + right.t.z * v;

            target.r.lerp(left.r, right.r, v);
            return target;
        }
    }
    export class Frame
    {
        fid: number;
        key: boolean;
        bonedata: PoseBoneMatrix[];
    }
    export class AniClipData
    {
        name: string;
        fps: number;
        loop: boolean;
        boneinfo: string[];
        subclips: SubClip[];
        frames: Frame[];

        static loadClip(buf: ArrayBuffer): AniClipData
        {
            var clipdata: AniClipData = new AniClipData();
            var read: Reader = new Reader(buf);
            clipdata.name = read.readString();
            clipdata.fps = read.readSingle();
            clipdata.loop = read.readUInt8() > 0;
            {//boneinfo
                var bcount = read.readUInt32();
                clipdata.boneinfo = [];
                for (var i = 0; i < bcount; i++)
                {
                    clipdata.boneinfo.push(read.readString());
                }
            }
            {
                //subclips
                clipdata.subclips = [];
                var scount = read.readUInt32();
                for (var i = 0; i < scount; i++)
                {
                    var sc = new SubClip();
                    sc.name = read.readString();
                    sc.loop = read.readUInt8() > 0;
                    sc.startframe = read.readUInt32();
                    sc.endframe = read.readUInt32();

                    clipdata.subclips.push(sc);
                }
            }
            {//frame
                clipdata.frames = [];
                var fcount = read.readUInt32();

                for (var i = 0; i < fcount; i++)
                {
                    var f = new Frame();
                    f.fid = read.readUInt32();
                    f.key = read.readUInt8() > 0;
                    clipdata.frames.push(f);

                    clipdata.frames[i].bonedata = [];
                    for (var ib = 0; ib < clipdata.boneinfo.length; ib++)
                    {
                        clipdata.frames[i].bonedata.push(new PoseBoneMatrix());
                        clipdata.frames[i].bonedata[ib].load(read,
                            i > 0 ? clipdata.frames[i - 1].bonedata[ib] : null);
                    }
                }
            }
            return clipdata;
        }


    }
}